package orm

import (
	"context"
	"fmt"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
	"gsms/pkg/mcontext"
	"time"


	// Register postgres driver
	_ "github.com/jinzhu/gorm/dialects/postgres"
	"go.uber.org/zap"
	gl "gorm.io/gorm/logger"
)

// Config mysql config.
type Config struct {
	DSN         string        `mapstructure:"dsn"`          // data source name.
	Active      int           `mapstructure:"active"`       // pool
	Idle        int           `mapstructure:"idle"`         // pool
	IdleTimeout time.Duration `mapstructure:"idle_timeout"` // connect max life time.
}
const (
	Silent gl.LogLevel = iota + 1
	Error
	Warn
	Info
)

type Logger struct {
	LogLevel      gl.LogLevel
	SlowThreshold time.Duration
}

func (l *Logger) LogMode(level gl.LogLevel) gl.Interface {
	newlogger := *l
	newlogger.LogLevel = level
	return &newlogger
}

func (l Logger) Info(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= Info {
		logger := mcontext.Logger(ctx)
		logger.Info(msg, zap.Any("sqlInfo", data))
	}
}

func (l Logger) Warn(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= Warn {
		logger := mcontext.Logger(ctx)
		logger.Warn(msg, zap.Any("sqlWarn", data))
	}
}

func (l Logger) Error(ctx context.Context, msg string, data ...interface{}) {
	if l.LogLevel >= Error {
		logger := mcontext.Logger(ctx)
		logger.Error(msg, zap.Any("sqlError", data))
	}
}

func (l Logger) Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) {
	if l.LogLevel > 0 {
		elapsed := time.Since(begin)
		logger := mcontext.Logger(ctx)
		switch {
		case err != nil && l.LogLevel >= Error:
			sql, rows := fc()
			logger.Error("sqlTrace", zap.Error(err),
				zap.Float64("elapsed", float64(elapsed.Nanoseconds())/1e6),
				zap.Any("rows", rows), zap.String("sql", sql))
		case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= Warn:
			sql, rows := fc()
			logger.Warn("sqlTrace", zap.Float64("elapsed_in_ms", float64(elapsed.Nanoseconds())/1e6),
				zap.Any("rows", rows), zap.String("sql", sql))
		case l.LogLevel >= Info:
			sql, rows := fc()
			logger.Info("sqlTrace", zap.Float64("elapsed_in_ms", float64(elapsed.Nanoseconds())/1e6),
				zap.Any("rows", rows), zap.String("sql", sql))
		}
	}

}


// NewPostgreSQL new PostgreSQL
func NewPostgreSQL(c *Config) (db *gorm.DB) {
	var err error
	l := &Logger{
		LogLevel:      Info,
		SlowThreshold: time.Second,
	}
	ctx := context.Background()
	log := mcontext.Logger(ctx)
	db, err = gorm.Open(postgres.Open(c.DSN), &gorm.Config{Logger: l})
	if err != nil {
		log.Panic(fmt.Sprintf("db dsn(%s) error: %v", c.DSN, err))
	}
	sqlDB, err := db.DB()
	if err != nil {
		log.Fatal("could not get sqlDB", zap.Error(err))
	}
	sqlDB.SetMaxIdleConns(c.Idle)
	sqlDB.SetMaxOpenConns(c.Active)
	sqlDB.SetConnMaxLifetime(c.IdleTimeout / time.Second)
	return
}
